using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;

namespace ODX.Core
{
    internal class SmartDataSetMerger
    {
        internal static void Merge(DataSet dst, DataSet src)
        {
            Merge(dst, src, MissingSchemaAction.Ignore);
        }
        internal static void Merge(DataSet dst, DataSet src, MissingSchemaAction missingSchemaAction)
        {
            foreach (DataTable src_dt in DataTablesOrderer.Sort(src))
            {
                if (!dst.Tables.Contains(src_dt.TableName))
                    continue;

                DataTable dst_dt = dst.Tables[src_dt.TableName];

                foreach (object _dst_c in dst_dt.Constraints)
                {
                    UniqueConstraint dst_c = _dst_c as UniqueConstraint;
                    if (dst_c != null && !dst_c.IsPrimaryKey)
                    {
                        foreach (DataRow src_dr in Select(src_dt))
                        {
                            object[] parameters;
                            string flt = GetFilter(dst_c, dst_dt, src_dr, out parameters);
                            flt = FilterParser.Parse(flt, delegate(int n)
                                                              {
                                                                  return
                                                                      DataSetValueFormatter.FormatValue(
                                                                          parameters[n]);
                                                              });

                            DataRow[] foundDstRows = Select(dst_dt, flt);
                            if (foundDstRows.Length == 0)
                                continue;

                            DataRow dst_dr = foundDstRows[0];

                            if (dst_dr.RowState == DataRowState.Unchanged)
                                dst_dr.Delete();
                            else
                                src_dr.Delete();
                        }
                    }
                }
            }

            DataSet changes = dst.GetChanges();
            dst.Merge(src, false, missingSchemaAction);
            if (changes != null)
                dst.Merge(changes, false, missingSchemaAction);
        }

        internal static string GetFilter(UniqueConstraint constraint, DataTable dst_dt, DataRow src_dr, out object[] parameters)
        {
            List<object> values = new List<object>();
            List<string> filter = new List<string>();
            foreach (DataColumn dst_c_dc in constraint.Columns)
            {
                object val;
                if (src_dr.RowState == DataRowState.Deleted)
                    val = src_dr[dst_c_dc.ColumnName, DataRowVersion.Original];
                else
                    val = src_dr[dst_c_dc.ColumnName];

                if (val == null || val is DBNull)
                    filter.Add(dst_c_dc.ColumnName + " IS NULL");
                else
                {
                    filter.Add(dst_c_dc.ColumnName + " = ?");
                    values.Add(val);
                }
            }

            List<string> pkFilter = new List<string>();
            foreach (DataColumn dst_pk_dc in dst_dt.PrimaryKey)
            {
                object val;
                if (src_dr.RowState == DataRowState.Deleted)
                    val = src_dr[dst_pk_dc.ColumnName, DataRowVersion.Original];
                else
                    val = src_dr[dst_pk_dc.ColumnName];

                if (val == null || val is DBNull)
                    pkFilter.Add("NOT (" + dst_pk_dc.ColumnName + " IS NULL)");
                else
                {
                    pkFilter.Add(dst_pk_dc.ColumnName + " <> ?");
                    values.Add(val);
                }
            }

            parameters = values.ToArray();
            return string.Format("{0} AND ({1})",
                                 string.Join(" AND ", filter.ToArray()),
                                 string.Join(" OR ", pkFilter.ToArray()));
        }

        private static DataRow[] Select(DataTable dt)
        {
            return Select(dt, null);
        }

        private static DataRow[] Select(DataTable dt, string filter)
        {
            ArrayList rows = new ArrayList();
            rows.AddRange(dt.Select(filter, null, DataViewRowState.CurrentRows));
            DataRow[] allDeleted = dt.Select(filter, null, DataViewRowState.Deleted);
            foreach (DataRow dr in allDeleted)
            {
                ArrayList pk = new ArrayList();
                foreach (DataColumn dc in dt.PrimaryKey)
                    pk.Add(dr[dc.ColumnName, DataRowVersion.Original]);
                if (dt.Rows.Find(pk.ToArray()) == null)
                    rows.Add(dr);
            }

            return (DataRow[])rows.ToArray(typeof(DataRow));
        }
    }

    internal static class DataSetValueFormatter
    {
        internal static string FormatValue(object value)
        {
            if (value is DateTime)
                return "'" + ((DateTime)value).ToString("o") + "'";
            if (value is string)
                return "'" + value + "'";
            return value.ToString();
        }
    }

    internal static class DataTablesOrderer
    {
        internal static ArrayList Sort(DataSet dataSet)
        {
            ArrayList tableList = new ArrayList(dataSet.Tables);
            ArrayList relationList = new ArrayList(dataSet.Relations);

            ProcessIt(tableList, relationList);

            return tableList;
        }

        private static void ProcessIt(ArrayList tableList, ArrayList relationList)
        {
            ArrayList tableRest = new ArrayList();
            ArrayList relationCopy = new ArrayList(relationList);

            foreach (DataRelation dr in relationList)
            {
                if (tableList.Contains(dr.ChildTable) && dr.ChildTable != dr.ParentTable)
                {
                    tableRest.Add(dr.ChildTable);
                    tableList.Remove(dr.ChildTable);
                }
            }
            foreach (DataRelation dr in relationList)
                if (tableList.Contains(dr.ParentTable))
                    relationCopy.Remove(dr);

            if (tableRest.Count > 0)
                ProcessIt(tableRest, relationCopy);

            tableList.AddRange(tableRest);
        }
    }
}
